home *** CD-ROM | disk | FTP | other *** search
/ Games of Daze / Infomagic - Games of Daze (Summer 1995) (Disc 1 of 2).iso / x2ftp / msdos / libs / ctask22d / tskasm.asm < prev    next >
Assembly Source File  |  1993-03-09  |  28KB  |  1,368 lines

  1. ;
  2. ;    --- Version 2.2 93-03-09 12:05 ---
  3. ;
  4. ;    CTask - Scheduler and miscellaneous utilities
  5. ;
  6. ;    Public Domain Software written by
  7. ;        Thomas Wagner
  8. ;        Ferrari electronic Gmbh
  9. ;        Beusselstrasse 27
  10. ;        D-1000 Berlin 21
  11. ;        Germany
  12. ;
  13. ;
  14. ; NOTE: Version 2.1 enables interrupts shortly after setting the in_sched
  15. ;    flag. This greatly reduces interrupt latency, but it has its
  16. ;    pitfalls. The main culprit is modification of the current TCB
  17. ;    while the scheduler is busy processing it. However, the situations
  18. ;    where a "foreign" TCB is modified are rare, so it is possible to
  19. ;    handle those cases in the appropriate places. Note that it was
  20. ;    never a good idea to make a task waiting or, worse yet, kill the
  21. ;    current task, while the scheduler was active. This was not detected
  22. ;    in previous releases, but would still cause crashes, even though
  23. ;    the interrupt was enabled only in the eligible waiting loop.
  24. ;
  25. ;    The only legitimate action is to make a task eligible while it
  26. ;    is being scheduled out. Since this only modifies the queue head
  27. ;    and task status, no adverse effects are possible. But making a task
  28. ;    waiting that is already scheduled out (as it was possible in
  29. ;    all versions up to 2.1) simply won't work. The wait action requires
  30. ;    that the scheduler switch the context away from the running task,
  31. ;    which it can't do when it's already active and there is no real
  32. ;    "running task". So the scheduler would immediately return, and 
  33. ;    the wait would never occur, most likely causing some very 
  34. ;    strange effects. Version 2.1 now explicitly checks for this error,
  35. ;    and will terminate the tasker if this should ever happen.
  36. ;
  37. ;    Other safeguards include not calling the INT 8 timer interrupt 
  38. ;    chain for early INT8 processing while the scheduler is active.
  39. ;    If INT8_EARLY is used, the tick chain is called from within
  40. ;    the interrupt handler. Since the interrupt could have hit 
  41. ;    while the scheduler was active, and the original INT 8
  42. ;    may have TSRs attached that call DOS, dangerous conflicts are
  43. ;    possible. For the same reason, the scheduler will set the In-DOS
  44. ;    flag while it is active, to keep TSR's activated by other
  45. ;    interrupts (esp. the keyboard) from calling DOS.
  46. ;    Using the same delayed processing as is used in INT 8 in 
  47. ;    the INT 9 handler was tried, but led to lost keystrokes depending
  48. ;    on system load.
  49. ;
  50. ;
  51. ;    Interrupts also are enabled in most other places in the scheduler,
  52. ;    except when traversing the queues.
  53. ;
  54. ;    Having interrupts enabled during the save/restore code also
  55. ;    eliminates a deadlock problem with floating point exceptions
  56. ;    on the 8087, which simplifies saving and restoring the '87 registers.
  57. ;
  58. ;
  59. ;    Version 2.2 eliminates a race condition problem introduced by
  60. ;    the above changes. If a task was made waiting, only the queue head
  61. ;    reflected this. Only after the scheduler had entered the task into
  62. ;    the appropriate queue would it have been safe to enable interrupts,
  63. ;    since an interrupt handler calling (indirectly) tsk_runable would
  64. ;    never see the task as waiting if it hit before this time.
  65. ;    To keep interrupts enabled, the logic was changed in that the
  66. ;    wait action now inserts the task into the appropriate queue
  67. ;    directly, without waiting for the scheduler to do it. Naturally,
  68. ;    this means a change here so that the scheduler doesn't touch
  69. ;    the queue if the task is already enqueued. Only if the task is not
  70. ;    in any queue is the queue head used to determine the place to
  71. ;    enqueue, which now can occur only when making a running task
  72. ;    eligible.
  73. ;
  74. ;**************************  CAUTION:  ***********************************
  75. ;
  76. ;    If the DOS flag is set, the scheduler uses an undocumented feature
  77. ;    of DOS versions >= 3.10 to save and restore certain variables
  78. ;    local to the DOS kernel. This allows for fast, secure task switching
  79. ;    with tasks owning different PSP's. To save and restore the complete
  80. ;    state of DOS on every task switch would not be feasible with any
  81. ;    other documented method.
  82. ;
  83. ;    NOTE that this method relies on certain inner workings of DOS
  84. ;    that may not be compatible with future releases or "compatible"
  85. ;    operating systems. It has been tested with all DOS versions
  86. ;    from 3.10 through 3.20, 3.30, up to MS-DOS 6.00 (Beta). It
  87. ;    also has been reported to work with DR-DOS 3.41.
  88. ;
  89. ;*************************************************************************
  90. ;
  91.     name    tskasm
  92. ;
  93.     include    tsk.mac
  94.     include    tskdeb.h
  95. ;
  96.     .tsk_model
  97. ;
  98.     public    tsk_scheduler
  99.     public    sched_int
  100. ;
  101.     Pubfunc    schedule
  102.     Pubfunc    yield
  103.     Pubfunc    c_schedule
  104. ;
  105.     Pubfunc    tsk_callfunc
  106.     Pubfunc    tsk_dis_int
  107.     Pubfunc    tsk_ena_int
  108.     Pubfunc    tsk_cli
  109.     Pubfunc    tsk_sti
  110.     Pubfunc    tsk_dseg
  111.     Pubfunc    tsk_flags
  112.     Pubfunc    tsk_outp
  113.     Pubfunc    tsk_inp
  114.     Pubfunc    tsk_inpw
  115.     Pubfunc    tsk_nop
  116.     Pubfunc    tsk_memcpy
  117. ;
  118.     IF    DOS
  119.     Pubfunc    tsk_get_dosswap
  120.     ENDIF
  121.     IF    CLOCK_MSEC
  122.     Pubfunc    tsk_timeout
  123.     ENDIF
  124. ;
  125.     IF    CHECKING
  126.     CGlbext    tsk_fatal_pmd
  127.     ENDIF
  128.     IFDEF    SERSNAP
  129.     Globext    tsk_cprint_getc
  130.     Globext    comsnapshot
  131.     ENDIF
  132. ;
  133.     IF    DEBUG AND DEB_TSKDIS
  134.     extrn    tsk_debtask: word
  135.     ENDIF
  136.     IF    DEBUG AND DEB_FLASHERS
  137.     Pubfunc    tsk_inccdis
  138.     extrn    tsk_debflash: word
  139.     ENDIF
  140.     IF    DEBUG
  141.     extrn    tsk_dtposn: dword
  142.     ENDIF
  143. ;
  144.     global_ext
  145. ;
  146. ;
  147. LSTACK_SIZE    =    512    ; local stack size in words
  148. ;
  149. INT_FLAG    =    2h    ; Int enable flag in upper byte of flag reg
  150. ;
  151. sr_flags    =    8    ; Offset of flag register on task stack
  152. sr_ds        =    2    ; Offset of DS on task stack
  153. ;
  154. psp_savoff    =    2eh    ; Offset of PSP stack pointer save dword
  155. ;
  156.     IF    FAR_STACKS
  157. ctask_stacks    segment word public 'CTASK_STACKS'
  158.     ELSE
  159.     .tsk_data
  160.     ENDIF
  161. ;
  162.     dw    LSTACK_SIZE dup(?)
  163. slocal_stack    label    word
  164. ;
  165.     IF    FAR_STACKS
  166. ctask_stacks    ends
  167.     .tsk_data
  168.     ENDIF
  169. ;
  170.     .tsk_edata
  171.     .tsk_code
  172. ;
  173. tsk_dgroup    dw    @CTASK_DATA
  174. ;
  175.     IF    FAR_STACKS
  176. stackseg    dw    SEG ctask_stacks
  177.     ELSE
  178. stackseg    equ    <tsk_dgroup>
  179.     ENDIF
  180. ;
  181. ;------------------------------------------------------------------------
  182. ;
  183. ;    enq    Enqueue tcb in queue. For local use only.
  184. ;        entry:    es:di = tcb to enqueue
  185. ;        exit:    -
  186. ;        uses:    ax, cx, si
  187. ;    NOTE:    Interrupts must be disabled on entry.
  188. ;
  189. enq    macro
  190. ;
  191.     push    ds
  192.     lds    cx,es:cqueue.q_next[di]    ; this is NULL if not in queue
  193.     mov    ax,ds
  194.     or    ax,cx
  195.     jnz    enq_end            ; don't touch if enqueued
  196.     lds    si,es:qhead[di]        ; queue head pointer
  197.     mov    ax,ds
  198.     or    ax,si    
  199.     jz    enq_end            ; nothing left to do if queue null
  200. ;
  201.     mov    cx,es:cqueue.q_el.q_prior[di]
  202.     mov    ax,es:cqueue.q_el.q_ini_prior[di]
  203.     mov    es:cqueue.q_el.q_prior[di],ax
  204.     lds    si,q_last[si]        ; last queue element
  205. ;
  206. enq_loop:
  207.     test    q_kind[si],Q_HEAD    ; at head?
  208.     jnz    enq_found        ; then insert
  209.     cmp    q_el.q_prior[si],cx    ; else check priority
  210.     jae    enq_found        ; if above or equal, insert
  211. ;
  212.     lds    si,q_prev[si]        ; backup one element
  213.     jmp    enq_loop        ; and try again
  214. ;
  215. enq_found:
  216.     mov    word ptr es:q_prev[di],si    ; elem->prev = curr
  217.     mov    word ptr es:q_prev+2[di],ds
  218.     mov    ax,word ptr q_next[si]        ; elem->next = curr->next;
  219.     mov    word ptr es:q_next[di],ax
  220.     mov    cx,word ptr q_next+2[si]
  221.     mov    word ptr es:q_next+2[di],cx
  222.     mov    word ptr q_next[si],di        ; curr->next = elem;
  223.     mov    word ptr q_next+2[si],es
  224.     mov    si,ax
  225.     mov    ds,cx
  226.     mov    word ptr q_prev[si],di        ; elem->next->prev = elem
  227.     mov    word ptr q_prev+2[si],es
  228. ;
  229. enq_end:
  230.     pop    ds
  231. ;
  232.     endm
  233. ;
  234. ;    upd_prior: Update priority of tasks in eligible queue.
  235. ;               Only activated if tsk_var_prior is nonzero.
  236. ;
  237. ;    entry:    ds:bx = global variable block
  238. ;    exit:    -
  239. ;    uses:    ax,di,es
  240. ;
  241. ;    NOTE:    Contrary to what earlier versions said, this loop
  242. ;        must be protected by interrupt disable.
  243. ;        Since it steps through a pointer chain, and this
  244. ;        pointer chain might be modified by external events
  245. ;        (a task becoming eligible to run), there is indeed
  246. ;        a danger of race conditions.
  247. ;
  248. upd_prior    macro
  249. ;
  250.     les    di,eligible_queue.q_first[bx]
  251. ;
  252. pinc_loop:
  253.     test    es:q_kind[di],Q_HEAD    ; queue head?
  254.     jnz    updp_end
  255.     inc    es:q_el.q_prior[di]
  256.     jnz    pinc_nxt
  257.     dec    es:q_el.q_prior[di]
  258. pinc_nxt:
  259.     les    di,es:q_next[di]
  260.     jmp    pinc_loop
  261. ;
  262. updp_end:
  263. ;
  264.     endm
  265. ;
  266. ;-------------------------------------------------------------------------
  267. ;
  268.     IF    DEBUG AND DEB_TSKDIS
  269. ;
  270. fill8    macro
  271.     mov    cx,10
  272.     mov    ax,0720h
  273.     rep stosw
  274.     endm
  275. ;
  276. @strcpy8    proc    near
  277.     mov    cx,8
  278.     mov    ah,7
  279. strcpyl:
  280.     lodsb
  281.     or    al,al
  282.     jz    strcpyfill
  283.     stosw
  284.     loop    strcpyl
  285. ;
  286. strcpyfill:
  287.     add    cx,2
  288.     mov    al,' '
  289.     rep stosw
  290.     ret
  291. ;
  292. @strcpy8    endp
  293. ;
  294.     ENDIF
  295. ;
  296.     IF    DEBUG AND DEB_FLASHERS
  297. ;
  298. ;    tsk_inccdis - increment counter on display
  299. ;
  300. ;    Entry:    AX is counter first digit offset
  301. ;        DS is CTask dgroup
  302. ;
  303. ;    Uses:    All registers preserved
  304. ;
  305. Localfunc    tsk_inccdis
  306. ;
  307.     push    ds
  308.     push    si
  309.     push    cx
  310.     lds    si,tsk_dtposn
  311.     add    si,ax
  312.     mov    cx,DEBFLASH_NDIGS
  313.     add    si,(DEBFLASH_NDIGS - 1) * 2
  314. incdis_loop:
  315.     inc    byte ptr [si]
  316.     cmp    byte ptr [si],'9'
  317.     jbe    incdis_end
  318.     mov    byte ptr [si],'0'
  319.     sub    si,2
  320.     loop    incdis_loop
  321. incdis_end:
  322.     pop    cx
  323.     pop    si
  324.     pop    ds
  325.     ret
  326. ;
  327. tsk_inccdis    endp
  328. ;
  329.     ENDIF
  330. ;
  331. ;-------------------------------------------------------------------------
  332. ;
  333. ;    The scheduler. Note that this routine is entered with the stack
  334. ;    set up as for an interrupt handler.
  335. ;
  336. tsk_scheduler    proc    far
  337. ;
  338.     cli            ; better safe than sorry
  339. ;
  340. ;    First, check if we're already in the scheduler. This might
  341. ;    happen if an interrupt handler schedules, and it would have
  342. ;    catastrophic results if the scheduler would be re-entered.
  343. ;    Previous versions used a code-segment based variable to store
  344. ;    this flag. Starting at version 2.0, the flag is located in the 
  345. ;    global variable block, to support ROM-based implementations, 
  346. ;    and to avoid multiple entries into the scheduler when code
  347. ;    sharing is not used in secondary invocations.
  348. ;
  349. ;    Version 2.1 sets the 'pretick' flag when the scheduler is busy.
  350. ;    This allows an immediate re-schedule if the schedule request
  351. ;    hit after loading the new task.
  352. ;
  353.     push    ds
  354.     push    bx
  355.     mov    ds,cs:tsk_dgroup
  356.     IF    SINGLE_DATA
  357.     cmp    tsk_glob_rec.in_sched,0    ; already in the scheduler?
  358.     je    sched_ok    ; continue if not
  359.     mov    tsk_glob_rec.pretick,1    ; mark schedule pending
  360.     ELSE
  361.     lds    bx,tsk_global
  362.     cmp    in_sched[bx],0    ; already in the scheduler?
  363.     je    sched_ok    ; continue if not
  364.     mov    pretick[bx],1    ; mark schedule pending
  365.     ENDIF
  366.     pop    bx
  367.     pop    ds        ; else return immediately
  368.     iret
  369. ;
  370. ;    Not the second time into the scheduler, set the in_sched flag,
  371. ;    and load the current task's TCB.
  372. ;    If we're running under DOS, additionally set the In-DOS flag
  373. ;    for the reasons outlined above.
  374. ;
  375. sched_ok:
  376.     IF    SINGLE_DATA
  377.     inc    tsk_glob_rec.in_sched
  378.     IF    DOS
  379.     push    ds
  380.     lds    bx,tsk_glob_rec.dos_in_use
  381.     inc    byte ptr ds:[bx]
  382.     pop    ds
  383.     ENDIF
  384.     lds    bx,tsk_glob_rec.current_task
  385.     ELSE
  386.     inc    in_sched[bx]
  387.     IF    DOS
  388.     push    ds
  389.     push    bx
  390.     lds    bx,dos_in_use[bx]
  391.     inc    byte ptr [bx]
  392.     pop    bx
  393.     pop    ds
  394.     ENDIF
  395.     lds    bx,current_task[bx]
  396.     ENDIF
  397.     push    ax
  398.     mov    ax,ds
  399.     or    ax,bx
  400.     pop    ax
  401. ;
  402. ;    Registers are stored in the TCB to keep stack usage minimal.
  403. ;    If there is no current task (i.e. it has been killed),
  404. ;    we can't store the registers.
  405. ;
  406.     IF    CHECKING
  407.     jnz    chk_rsave
  408.     jmp    no_rsave
  409. chk_rsave:
  410.     CHECK_TCBPTR_R    ds,bx,"scheduler: current task"
  411.     ELSE
  412.     jz    no_rsave
  413.     ENDIF
  414.     cmp    state[bx],ST_RUNNING
  415.     jne    store_regs
  416.     mov    state[bx],ST_ELIGIBLE
  417. ;
  418. store_regs:
  419.     sti
  420.     mov    t_sp[bx],sp
  421.     mov    t_ss[bx],ss
  422.     mov    t_ax[bx],ax
  423.     mov    t_cx[bx],cx
  424.     mov    t_dx[bx],dx
  425.     mov    t_bp[bx],bp
  426.     mov    t_si[bx],si
  427.     mov    t_di[bx],di
  428.     mov    t_es[bx],es
  429.     mov    bp,sp
  430.     or    byte ptr sr_flags+1[bp],INT_FLAG ; enable interrupts on return
  431.     mov    ax,sr_ds[bp]
  432.     mov    t_ds[bx],ax
  433. ;
  434. ;    This is the entry when restarting the scheduler.
  435. ;    Note that registers don't have to be saved again.
  436. ;
  437. sched_restart:
  438. no_rsave:
  439.     cli
  440.     mov    ss,cs:stackseg        ; switch to local stack
  441.     mov    sp,offset slocal_stack
  442.     sti
  443.     cld
  444.     mov    ds,cs:tsk_dgroup    ; establish addressing of our vars
  445.     IF    DEBUG AND DEB_FLASHERS
  446.     cmp    tsk_debflash,0
  447.     je    debdd0
  448.     mov    ax,DEBP_CNTSCHED
  449.     call    tsk_inccdis
  450. debdd0:
  451.     ENDIF
  452.     IF    DEBUG AND DEB_TSKDIS
  453.     cmp    tsk_debtask,0
  454.     je    debdd1
  455.     les    di,tsk_dtposn
  456.     mov    byte ptr es:[di],'*'
  457. debdd1:
  458.     ENDIF
  459.     IF    SINGLE_DATA
  460.     mov    bx,offset tsk_glob_rec
  461.     ELSE
  462.     lds    bx,tsk_global
  463.     ENDIF
  464. ;
  465.     cmp    var_prior[bx],0
  466.     je    no_var_pri
  467. ;
  468.     cli
  469.     upd_prior            ; update priorities in eligible queue
  470.     sti
  471. ;
  472. no_var_pri:
  473. ;
  474.     and    preempt[bx],1        ; Turn off temp preempt flag
  475. ;
  476.     les    di,current_task[bx]    ; get current tcb again
  477.     mov    ax,es            ; check if NULL (current task killed)
  478.     or    ax,di
  479.     IF    NOT CHECKING
  480.     jz    no_current
  481.     ELSE
  482.     jnz    do_chkstk
  483.     jmp    no_current
  484. ;
  485. do_chkstk:
  486.     push    ds
  487.     lds    si,es:stkbot[di]
  488.     mov    cx,8
  489.     xor    ah,ah
  490. chkstk:
  491.     lodsb
  492.     cmp    ah,al
  493.     jne    stkoflo
  494.     inc    ah
  495.     loop    chkstk
  496.     jmp    short chkstend
  497. ;
  498. stkmsg    db    "Stack Overflow",0
  499. ;
  500. stkoflo:
  501.     callp    tsk_fatal_pmd,<<cs,#stkmsg>>
  502. ;
  503. chkstend:
  504.     pop    ds
  505.     ENDIF
  506. ;
  507. ;    If there is a scheduler-entry routine in the current task's
  508. ;    TCB, call it. (This is used by the DOS handler module)
  509. ;    NOTE: Interrupts are still enabled.
  510. ;
  511.     IF    DOS
  512.     mov    ax,word ptr es:sched_ent_func[di]
  513.     or    ax,word ptr es:sched_ent_func[di]+2
  514.     jz    no_schentcall
  515. ;
  516.     push    es
  517.     push    di
  518.     push    ds
  519.     push    bx
  520.     call    es:sched_ent_func[di]
  521.     pop    bx
  522.     pop    ds
  523.     pop    di
  524.     pop    es
  525. ;
  526. no_schentcall:
  527.     ENDIF
  528. ;
  529.     cli
  530.     enq                ; Enqueue current task
  531.     sti
  532. ;
  533. no_current:
  534.     lea    si,eligible_queue[bx]
  535. ;
  536. ;    Now we enter an enabled loop if there is no task in the
  537. ;    eligible queue.
  538. ;
  539. wait_elig:
  540.     IFDEF    SERSNAP
  541.     callp    tsk_cprint_getc
  542.     jz    nosnap
  543.     or    al,20h
  544.     cmp    al,'s'
  545.     jne    nosnap
  546.     push    bx
  547.     callp    comsnapshot
  548.     pop    bx
  549. nosnap:
  550.     ENDIF
  551.     IF    DEBUG AND DEB_TSKDIS
  552.     push    ds
  553.     mov    ds,cs:tsk_dgroup
  554.     cmp    tsk_debtask,0
  555.     je    debdd2
  556.     les    di,tsk_dtposn
  557.     add    di,DEBP_ELIGTSK
  558.     pop    ds
  559.     push    ds
  560.     push    si
  561.     lds    si,q_first[si]
  562.     test    q_kind[si],Q_HEAD
  563.     jnz    deb101
  564.     push    si
  565.     lea    si,tname.nname[si]
  566.     call    @strcpy8
  567.     pop    si
  568.     lds    si,q_next[si]
  569.     test    q_kind[si],Q_HEAD
  570.     jnz    deb102
  571.     lea    si,tname.nname[si]
  572.     call    @strcpy8
  573.     jmp    short deb105
  574. ;
  575. deb101:
  576.     fill8
  577. deb102:
  578.     fill8
  579. deb105:
  580.     pop    si
  581. debdd2:
  582.     pop    ds
  583.     ENDIF
  584.     jmp    $+2            ; allow ints
  585.     cli
  586.     les    di,q_first[si]
  587.     test    es:q_kind[di],Q_HEAD
  588.     jz    not_empty        ; jump if not
  589. ;
  590.     sti                       ; enable interrupts
  591.     IF    DEBUG AND DEB_FLASHERS
  592.     cmp    tsk_debflash,0
  593.     je    debdidle
  594.     mov    ax,DEBP_CNTIDLE
  595.     call    tsk_inccdis
  596. debdidle:
  597.     ENDIF
  598.     jmp    $+2
  599.     jmp    wait_elig
  600. ;
  601. ;    Eligible queue not empty, activate first eligible task.
  602. ;    First, take it off the queue.
  603. ;    The preemption tick flag is cleared here. If it should be
  604. ;    set by an interrupt handler trying to schedule after this point,
  605. ;    we immediately re-schedule when hitting the end.
  606. ;
  607. not_empty:
  608.     CHECK_TCBPTR_R    es,di,"scheduler: eligible task"
  609.     mov    pretick[bx],0        ; No preemption tick
  610.     push    di
  611.     push    es
  612.     mov    bp,sp            ; address new pointer thru BP
  613. ;
  614.     mov    ax,word ptr es:cqueue.q_next[di] ; next in eligible queue
  615.     mov    cx,word ptr es:cqueue.q_next+2[di]
  616.     mov    word ptr es:cqueue.q_next[di],0    ; mark not in queue
  617.     mov    word ptr es:cqueue.q_next+2[di],0
  618.     mov    di,ax
  619.     mov    es,cx
  620.     mov    word ptr q_first[si],di
  621.     mov    word ptr q_first+2[si],es    ; eligible_queue.first = next
  622.     mov    word ptr es:q_prev[di],si
  623.     mov    word ptr es:q_prev+2[di],ds    ; next->prev = eligible_queue
  624. ;
  625. ;    Again, interrupts can be enabled here.
  626. ;
  627.     sti
  628. ;
  629. ;    Now check if the new task is the same as the old one.
  630. ;    If that's the case, we skip the save/restore function calls,
  631. ;    and the DOS variable copy.
  632. ;
  633.     les    di,current_task[bx]    ; the previously running task
  634.     mov    ax,es
  635.     cmp    ax,[bp]            ; same as the new one ?
  636.     jne    chk_sav            ; jump if not same segment
  637.     cmp    di,2[bp]
  638.     jne    chk_sav            ; jump if not same offset
  639.     jmp    sched_complete        ; don't save/restore if same task
  640. ;
  641. chk_sav:
  642.     or    ax,di
  643.     jz    set_new_task        ; don't save if no previous
  644. ;
  645. ;    First, call the save function if one is defined in the old TCB.
  646. ;
  647.     mov    ax,word ptr es:save_func[di]
  648.     or    ax,word ptr es:save_func+2[di]
  649.     jz    no_savfcall        ; jump if no save function
  650. ;
  651.     push    ds            ; save our registers
  652.     push    bx
  653.     push    es
  654.     push    di
  655. ;
  656.     push    es            ; push TCB address
  657.     push    es            ; push segment again
  658.     pop    ds            ; and put into DS
  659.     push    di
  660. ;    
  661.     call    es:save_func[di]    ; call function
  662.         add     sp,4
  663. ;
  664.         pop     di            ; restore registers
  665.         pop     es
  666.         pop     bx
  667.         pop     ds
  668. ;
  669. ;    Save the DOS variables, and the DOS-related interrupt vectors
  670. ;    in the old TCB.
  671. ;
  672. no_savfcall:
  673.     IF    DOS
  674.     mov    es:t_new[di],0        ; task is no longer new
  675. ;
  676.     push    ds
  677.     push    di
  678.     mov    ax,ds
  679.     mov    ds,es:base_psp[di]    ; get base PSP address
  680.     mov    si,psp_savoff        ; offset to save (caller's SS:SP)
  681.     add    di,psp_sssp        ; destination
  682.     movsw                ; save two words
  683.     movsw
  684.     mov    ds,ax
  685.     mov    cx,l_swap[bx]        ; swap area addr & size
  686.     jcxz    no_swap_sav
  687.     lds    si,dos_vars[bx]
  688.     rep movsb
  689. ;
  690. no_swap_sav:
  691.     xor    cx,cx            ; copy int21-24 interrupt vectors
  692.     mov    ds,cx
  693.     mov    si,21h*4
  694.     mov    cx,8
  695.     rep movsw
  696. ;
  697.     pop    di
  698.     pop    ds
  699.     ENDIF
  700. ;
  701. ;    if EMS support is installed, call the page map save function.
  702. ;
  703.     IF    EMS
  704.     mov    ax,word ptr ems_save[bx]
  705.     or    ax,word ptr ems_save+2[bx]
  706.     jz    no_emssav
  707.     call    ems_save[bx]
  708. ;
  709. no_emssav:
  710.     ENDIF
  711. ;
  712. ;    If the NDP is active, save the NDP context.
  713. ;
  714.     IF    NDP
  715.     test    es:flags[di],F_USES_NDP
  716.     jz    no_ndpsave
  717.     wait
  718.     fsave    es:ndpsave[di]
  719. ;
  720. no_ndpsave:
  721.     ENDIF
  722. ;
  723. ;    Save complete. The new TCB becomes the current task.
  724. ;
  725. set_new_task:
  726.     IF    DEBUG AND DEB_TSKDIS
  727.     push    ds
  728.     mov    ds,cs:tsk_dgroup
  729.     cmp    tsk_debtask,0
  730.     je    deb205
  731.     les    di,tsk_dtposn
  732.     pop    ax            ; saved DS
  733.     pop    ds            ; the new task's TCB
  734.     pop    si
  735.     push    si
  736.     push    ds
  737.     push    ax            ; saved DS
  738.     mov    byte ptr es:[di],'+'
  739.     add    di,DEBP_CURRTSK
  740.     lea    si,tname.nname[si]
  741.     call    @strcpy8
  742.     pop    ds
  743.     push    ds
  744.     lds    si,current_task[bx]    ; the previous task
  745.     lea    si,tname.nname[si]
  746.     call    @strcpy8
  747.     pop    ds
  748.     push    ds
  749.     lds    si,eligible_queue.q_first[bx]
  750.     test    q_kind[si],Q_HEAD
  751.     jnz    deb201
  752.     push    si
  753.     lea    si,tname.nname[si]
  754.     call    @strcpy8
  755.     pop    si
  756.     lds    si,q_next[si]
  757.     test    q_kind[si],Q_HEAD
  758.     jnz    deb202
  759.     lea    si,tname.nname[si]
  760.     call    @strcpy8
  761.     jmp    short deb205
  762. ;
  763. deb201:
  764.     fill8
  765. deb202:
  766.     fill8
  767. deb205:
  768.     pop    ds
  769.     ENDIF
  770.     pop    es
  771.     pop    di
  772.     cli
  773.     mov    word ptr current_task[bx],di     ; set tcb into current
  774.     mov    word ptr current_task+2[bx],es
  775.     sti
  776. ;
  777. ;    if EMS support is installed, call the page map restore function.
  778. ;
  779.     IF    EMS
  780.     mov    ax,word ptr ems_rest[bx]
  781.     or    ax,word ptr ems_rest+2[bx]
  782.     jz    no_emsrest
  783.     call    ems_rest[bx]
  784. no_emsrest:
  785.     ENDIF
  786. ;
  787. ;    Now check for a restore function in the new TCB.
  788. ;
  789.     mov    ax,word ptr es:rest_func[di]
  790.     or    ax,word ptr es:rest_func+2[di]
  791.     jz    no_resfcall            ; jump if no restore function
  792. ;
  793.     push    bx                ; save our regs
  794.     push    ds
  795.     push    di
  796.     push    es
  797. ;
  798.     push    es                ; push TCB addr
  799.     push    es                ; push segment again
  800.     pop    ds                ; and put into DS
  801.     push    di
  802. ;
  803.     call    es:rest_func[di]    ; call restore function
  804.         add     sp,4
  805. ;
  806.         pop     es
  807.         pop     di
  808.         pop     ds
  809.         pop     bx
  810. ;
  811. no_resfcall:
  812. ;
  813. ;    If NDP is enabled, restore 80x87 context.
  814. ;
  815.     IF    NDP
  816.     test    es:flags[di],F_USES_NDP
  817.     jz    no_ndprest
  818.     cmp    es:t_new[di],0        ; is this TCB a fresh one?
  819.     jne    no_ndprest        ; then NDP state isn't valid
  820. ;
  821.     wait
  822.     frstor    es:ndpsave[di]
  823. ;
  824. no_ndprest:
  825.     ENDIF
  826. ;
  827. ;    Restore DOS variables and int vectors from new TCB.
  828. ;
  829.     IF    DOS
  830. ;;    cmp    es:t_new[di],0        ; is this TCB a fresh one?
  831. ;;    jne    sched_complete        ; then the DOS info isn't valid.
  832. ;
  833.     push    di
  834.     push    es
  835.     push    ds
  836.     mov    dx,ds
  837.     mov    ax,es
  838.     mov    ds,ax
  839.     mov    si,di
  840.     add    si,base_psp
  841.     lodsw
  842.     mov    es,ax
  843.     mov    di,psp_savoff        ; offset to restore (caller's SS:SP)
  844.     cli
  845.     movsw                ; restore two words
  846.     movsw
  847.     sti
  848. ;
  849.     mov    ax,ds
  850.     mov    ds,dx
  851.     mov    cx,l_swap[bx]
  852.     jcxz    no_swap_rest
  853.     les    di,dos_vars[bx]
  854.     mov    ds,ax
  855.     rep movsb
  856. ;
  857. no_swap_rest:
  858.     mov    ds,ax
  859.     mov    di,21h*4        ; restore int21-24
  860.     xor    cx,cx
  861.     mov    es,cx
  862.     mov    cx,8
  863.     cli
  864.     rep movsw
  865.     sti
  866. ;
  867.     pop    ds
  868.     pop    es
  869.     pop    di
  870.     ENDIF
  871. ;
  872. ;    And that's it. Wrap it up by resetting the priority, 
  873. ;    restoring the registers, resetting the in_sched flag, 
  874. ;    and returning to the task.
  875. ;    Note: to allow keeping interrupts enabled as long as possible,
  876. ;    the in_sched flag is cleared after reloading the registers.
  877. ;
  878. sched_complete:
  879.     mov    ax,es:q_el.q_ini_prior[di]    ; reset current tasks priority
  880.     mov    es:q_el.q_prior[di],ax
  881.     mov    es:state[di],ST_RUNNING        ; set task state
  882. ;
  883.     push    es
  884.     push    di
  885.     IF    DEBUG AND DEB_TSKDIS
  886.     mov    ds,cs:tsk_dgroup
  887.     cmp    tsk_debtask,0
  888.     je    debddx
  889.     les    di,tsk_dtposn
  890.     mov    byte ptr es:[di],' '
  891. debddx:
  892.     ENDIF
  893.     pop    bx
  894.     pop    ds
  895.     mov    es,t_es[bx]            ; restore all registers
  896.     mov    di,t_di[bx]
  897.     mov    si,t_si[bx]
  898.     mov    bp,t_bp[bx]
  899.     mov    dx,t_dx[bx]
  900.     mov    cx,t_cx[bx]
  901.     mov    ax,t_ax[bx]
  902.     mov    ss,t_ss[bx]
  903.     mov    sp,t_sp[bx]
  904. ;
  905. ;    All done except for resetting the in_sched flag.
  906. ;    Now we check the preemption tick flag, and restart the schedule
  907. ;    if it is set. To avoid needless reschedules, the priority of the
  908. ;    current task is compared to the priority of the first task in
  909. ;    the eligible queue. If there is no task eligible, or if its
  910. ;    priority is less or equal to the current task, the current task
  911. ;    is allowed to continue.
  912. ;
  913.     mov    ds,cs:tsk_dgroup
  914.     cli
  915.     IF    SINGLE_DATA
  916.     cmp    tsk_glob_rec.pretick,0
  917.     je    sched_end
  918.     mov    bx,offset tsk_glob_rec
  919.     ELSE
  920.     lds    bx,tsk_global
  921.     cmp    pretick[bx],0
  922.     je    sched_end
  923.     ENDIF
  924. ;
  925.     push    es
  926.     push    di
  927.     les    di,current_task[bx]    ; get current tcb again
  928.     lds    bx,eligible_queue.q_first[bx]
  929.     test    q_kind[bx],Q_HEAD
  930.     jnz    no_restart
  931.     mov    bx,cqueue.q_el.q_prior[bx]    ; prior of eligible task
  932.     cmp    bx,es:cqueue.q_el.q_prior[di]    ; prior of current task
  933.     jbe    no_restart
  934. ;
  935. ;    Note: registers don't have to be popped, since the stack
  936. ;    is reset anyway at the sched_restart entry.
  937. ;
  938. do_rest:
  939.     jmp    sched_restart
  940. ;
  941. ;
  942. no_restart:
  943.     pop    di
  944.     pop    es
  945.     mov    ds,cs:tsk_dgroup
  946.     IF    NOT SINGLE_DATA
  947.     lds    bx,tsk_global
  948.     ENDIF
  949. ;
  950. sched_end:
  951.     IF    SINGLE_DATA
  952.     mov    tsk_glob_rec.in_sched,0
  953.     IF    DOS
  954.     lds    bx,tsk_glob_rec.dos_in_use
  955.     dec    byte ptr [bx]
  956.     ENDIF
  957.     ELSE
  958.     mov    in_sched[bx],0
  959.     IF    DOS
  960.     lds    bx,dos_in_use[bx]
  961.     dec    byte ptr [bx]
  962.     ENDIF
  963.     ENDIF
  964. ;
  965.     pop    bx
  966.     pop    ds
  967.     iret
  968. ;
  969. tsk_scheduler    endp
  970. ;
  971. ;
  972. ;--------------------------------------------------------------------------
  973. ;
  974. ;
  975. ;    sched_int  
  976. ;
  977. ;    Is the scheduler entry for interrupt handlers.
  978. ;    It checks if preemption is allowed, returning if not.
  979. ;    The stack is assumed to be set up as on interrupt entry.
  980. ;    
  981. sched_int    proc    far
  982. ;
  983.     push    ds
  984.     push    bx
  985.     mov    ds,cs:tsk_dgroup
  986.     IF    SINGLE_DATA
  987.     mov    bx,offset tsk_glob_rec
  988.     ELSE
  989.     lds    bx,tsk_global
  990.     ENDIF
  991.     cmp    preempt[bx],0        ; preempt flags 0?
  992.     jne    no_sched1        ; no scheduling if set
  993.     lds    bx,current_task[bx]    ; current running task
  994.     test    flags[bx],F_CRIT    ; preemption allowed for this task?
  995.     jnz    no_sched        ; no scheduling if flag set
  996.     pop    bx            ; else go schedule
  997.     pop    ds
  998.     jmp    tsk_scheduler
  999. ;
  1000. no_sched:
  1001.     mov    ds,cs:tsk_dgroup
  1002.     IF    SINGLE_DATA
  1003.     mov    bx,offset tsk_glob_rec
  1004.     ELSE
  1005.     lds    bx,tsk_global
  1006.     ENDIF
  1007. no_sched1:
  1008.     mov    pretick[bx],1        ; Mark preemption pending
  1009.     pop    bx
  1010.     pop    ds
  1011.     iret
  1012. ;
  1013. sched_int    endp
  1014. ;
  1015. ;
  1016. ;    void far schedule (void)
  1017. ;
  1018. ;    Entry for calling the scheduler. Rearranges the stack to
  1019. ;    contain flags.
  1020. ;    NOTE: Uses ax,bx.
  1021. ;
  1022. Globalfunc    schedule
  1023. ;
  1024.     IF    NEAR_CODE
  1025.     pop    ax
  1026.     pushf
  1027.     push    cs
  1028.     push    ax
  1029.     ELSE
  1030.     pop    ax
  1031.     pop    bx
  1032.     pushf
  1033.     push    bx
  1034.     push    ax
  1035.     ENDIF
  1036.     cli
  1037.     jmp    tsk_scheduler
  1038. ;
  1039. schedule    endp
  1040. ;
  1041. ;
  1042. ;    void far yield (void)
  1043. ;
  1044. ;    Entry for calling the scheduler with priority temporarily
  1045. ;       set to zero. Rearranges the stack to contain flags.
  1046. ;    NOTE: Uses ax,bx, es.
  1047. ;
  1048. Globalfunc    yield
  1049. ;
  1050.     IF    NEAR_CODE
  1051.     pop    ax
  1052.     pushf
  1053.     push    cs
  1054.     push    ax
  1055.     ELSE
  1056.     pop    ax
  1057.     pop    bx
  1058.     pushf
  1059.     push    bx
  1060.     push    ax
  1061.     ENDIF
  1062. ;
  1063.     IF    SINGLE_DATA
  1064.     IFDEF    LOAD_DS
  1065.     mov    es,cs:tsk_dgroup
  1066.     les    bx,es:tsk_glob_rec.current_task
  1067.     ELSE
  1068.     les    bx,tsk_glob_rec.current_task
  1069.     ENDIF
  1070.     ELSE
  1071.     IFDEF    LOAD_DS
  1072.     mov    es,cs:tsk_dgroup
  1073.     les    bx,es:tsk_global
  1074.     ELSE
  1075.     les    bx,tsk_global
  1076.     ENDIF
  1077.         les     bx,es:current_task[bx]
  1078.     ENDIF
  1079.         cli
  1080.         mov     es:q_el.q_prior[bx],0
  1081.     jmp    tsk_scheduler
  1082. ;
  1083. yield    endp
  1084. ;
  1085. ;
  1086. ;    void far c_schedule (void)
  1087. ;
  1088. ;    Entry for conditionally calling the scheduler. Rearranges 
  1089. ;    the stack to contain flags, then jumps to _sched_int.
  1090. ;    NOTE: Uses ax,bx.
  1091. ;
  1092. Globalfunc    c_schedule
  1093. ;
  1094.     IF    NEAR_CODE
  1095.     pop    ax
  1096.     pushf
  1097.     push    cs
  1098.     push    ax
  1099.     ELSE
  1100.     pop    ax
  1101.     pop    bx
  1102.     pushf
  1103.     push    bx
  1104.     push    ax
  1105.     ENDIF
  1106.     cli
  1107.     jmp    sched_int
  1108. ;
  1109. c_schedule    endp
  1110. ;
  1111. ;--------------------------------------------------------------------------
  1112. ;
  1113. ;    void tsk_callfunc (farptr funcad, farptr param)
  1114. ;
  1115. ;    Calls the given function, placing the segment address of
  1116. ;    the parameter into the DS register.
  1117. ;
  1118. Globalfunc    tsk_callfunc,<uses ds, funcad: far ptr, param: far ptr>
  1119. ;
  1120.     lds    ax,param
  1121.     push    ds
  1122.     push    ax
  1123.     call    funcad
  1124.     add    sp,4
  1125.     ret
  1126. tsk_callfunc    endp
  1127. ;
  1128. ;
  1129. ;    word tsk_dseg (void)
  1130. ;
  1131. ;    Returns current contents of DS register.
  1132. ;
  1133. Globalfunc    tsk_dseg
  1134.     mov    ax,ds
  1135.     ret
  1136. tsk_dseg    endp
  1137. ;
  1138. ;
  1139. ;    word tsk_flags (void)
  1140. ;
  1141. ;    Returns current contents of Flag register.
  1142. ;
  1143. Globalfunc    tsk_flags
  1144.     pushf
  1145.     pop    ax
  1146.     ret
  1147. tsk_flags    endp
  1148. ;
  1149. ;
  1150. ;    int tsk_dis_int (void)
  1151. ;
  1152. ;    Returns current state of the interrupt flag (1 if ints were 
  1153. ;    enabled), then disables interrupts.
  1154. ;
  1155. Globalfunc    tsk_dis_int
  1156. ;
  1157.     pushf
  1158.     pop    ax
  1159.     mov    al,ah
  1160.     shr    al,1
  1161.     and    ax,1
  1162.     cli
  1163.     ret
  1164. ;
  1165. tsk_dis_int    endp
  1166. ;
  1167. ;
  1168. ;    void far tsk_ena_int (int state)
  1169. ;
  1170. ;    Enables interrupts if 'state' is nonzero.
  1171. ;
  1172. Globalfunc    tsk_ena_int,<istate: word>
  1173. ;
  1174.     cmp    istate,0
  1175.     je    teiend
  1176.     sti
  1177. teiend:
  1178.     ret
  1179. ;
  1180. tsk_ena_int    endp
  1181. ;
  1182. ;
  1183. ;    tsk_cli/tsk_sti: disable/enable int
  1184. ;    NOTE: These routines are normally replaced by intrinsics.
  1185. ;
  1186. Globalfunc    tsk_cli
  1187.     cli
  1188.     ret
  1189. tsk_cli    endp
  1190. ;
  1191. ;
  1192. Globalfunc tsk_sti
  1193.     sti
  1194.     ret
  1195. tsk_sti    endp
  1196. ;
  1197. ;
  1198. ;    tsk_inp/tsk_outp: input/output from/to port
  1199. ;    NOTE: These routines are normally replaced by intrinsics,
  1200. ;          except for Turbo C tsk_inpw.
  1201. ;
  1202. Globalfunc tsk_inp,<port: word>
  1203. ;
  1204.     mov    dx,port
  1205.     in    al,dx
  1206.     xor    ah,ah
  1207.     ret
  1208. ;
  1209. tsk_inp    endp
  1210. ;
  1211. ;
  1212. Globalfunc tsk_inpw,<port: word>
  1213. ;
  1214.     mov    dx,port
  1215.     in    ax,dx
  1216.     ret
  1217. ;
  1218. tsk_inpw    endp
  1219. ;
  1220. ;
  1221. Globalfunc tsk_outp,<port: word, val: word>
  1222. ;
  1223.     mov    dx,port
  1224.     mov    al,byte ptr(val)
  1225.     out    dx,al
  1226.     ret
  1227. ;
  1228. tsk_outp    endp
  1229. ;
  1230. ;
  1231. ;    void tsk_nop (void)
  1232. ;
  1233. ;    Do nothing. Used for very short delays.
  1234. ;
  1235. Globalfunc    tsk_nop
  1236. ;
  1237.     jmp    short tnop1
  1238. tnop1:
  1239.     jmp    short tnop2
  1240. tnop2:
  1241.     ret
  1242. ;
  1243. tsk_nop    endp
  1244. ;
  1245. ;
  1246. ;    void tsk_memcpy (farptr dest, farptr src, word nbytes)
  1247. ;
  1248. Globalfunc    tsk_memcpy,<uses ds si di, dest: far ptr, src: far ptr, len: word>
  1249. ;
  1250.     mov    cx,len
  1251.     lds    si,src
  1252.     les    di,dest
  1253.     xor    bx,bx
  1254.     shr    cx,1
  1255.     rcl    bx,1
  1256.     jcxz    cpy_byte
  1257.     rep movsw
  1258. cpy_byte:
  1259.     or    bx,bx
  1260.     jz    cpy_end
  1261.     movsb
  1262. cpy_end:
  1263.     ret
  1264. ;
  1265. tsk_memcpy    endp
  1266. ;
  1267. ;
  1268.     IF    CLOCK_MSEC
  1269. ;
  1270. ;    dword Localfunc tsk_timeout (dword tout)
  1271. ;
  1272. ;    Translates milliseconds to clock ticks using integer arithmetic.
  1273. ;    Routine provided by Chris Blum.
  1274. ;
  1275. Localfunc    tsk_timeout,<tout:dword>
  1276. ;
  1277.     mov    dh,byte ptr tout
  1278.     mov    dl,byte ptr tout+3
  1279.     mov    ax,word ptr tout+1
  1280.     or    dx,dx
  1281.     jnz    tsk_timmax
  1282.     or    ax,ax
  1283.     jz    tsk_timok
  1284. tsk_timmax:
  1285.     push    ds
  1286.     mov    ds,cs:tsk_dgroup
  1287.     IF    SINGLE_DATA
  1288.     mov    bx,offset tsk_glob_rec
  1289.     ELSE
  1290.     lds    bx,tsk_global
  1291.     ENDIF
  1292.     mov    cx,tick_factor[bx]
  1293.     pop    ds
  1294.     xor    dh,dh
  1295.     cmp    cx,dx
  1296.     jbe    tsk_timhi
  1297.     div    cx
  1298.     mov    bx,ax
  1299.     xor    al,al
  1300.     mov    ah,byte ptr tout
  1301.     div    cx
  1302.     inc    cx
  1303.     shr    cx,1
  1304.     cmp    dx,cx
  1305.     mov    dx,bx
  1306.     jb    tsk_timmin
  1307.     add    ax,1
  1308.     adc    dx,0
  1309.     jnc    tsk_timok
  1310. tsk_timhi:
  1311.     mov    ax,0ffffh
  1312.     mov    dx,ax
  1313.     jmp short tsk_timok
  1314. tsk_timmin:
  1315.     or    ax,ax
  1316.     jnz    tsk_timok
  1317.     or    dx,dx
  1318.     jnz    tsk_timok
  1319.     inc    ax
  1320. tsk_timok:
  1321.     ret
  1322. ;
  1323. tsk_timeout    endp
  1324. ;
  1325.     ENDIF
  1326. ;
  1327.     IF    DOS
  1328. ;
  1329. Localfunc    tsk_get_dosswap,<uses ds si di, tcbp:far ptr>
  1330. ;
  1331.     IFDEF    LOAD_DS
  1332.     mov    ds,cs:tsk_dgroup
  1333.     ENDIF
  1334.     IF    SINGLE_DATA
  1335.     mov    bx,offset tsk_glob_rec
  1336.     ELSE
  1337.     lds    bx,tsk_global
  1338.     ENDIF
  1339.     les    di,tcbp
  1340.     mov    ax,ds
  1341.     mov    ds,es:base_psp[di]    ; get base PSP address
  1342.     mov    si,psp_savoff        ; offset to save (caller's SS:SP)
  1343.     add    di,psp_sssp        ; destination
  1344.     movsw                ; save two words
  1345.     movsw
  1346.     mov    ds,ax
  1347.     mov    cx,l_swap[bx]        ; swap area addr & size
  1348.     jcxz    gd_no_swap
  1349.     lds    si,dos_vars[bx]
  1350.     rep movsb
  1351. ;
  1352. gd_no_swap:
  1353.     xor    cx,cx            ; copy int21-24 interrupt vectors
  1354.     mov    ds,cx
  1355.     mov    si,21h*4
  1356.     mov    cx,8
  1357.     rep movsw
  1358. ;
  1359.     ret
  1360. ;
  1361. tsk_get_dosswap    endp
  1362. ;
  1363.     ENDIF
  1364. ;
  1365.     .tsk_ecode
  1366.     end
  1367.  
  1368.